在项目中使用eclipse编写了一个Android的Library工程,现需要将工程导出成jar包,并且混淆,然后提交给外部使用。要求除了外部需要调用的接口,其他部分需要proguard混淆和优化。

下面是实现的过程。

导出jar包

获取未混淆的jar包,这里有两种方式:

  1. 使用eclipse的export导出功能
  2. 直接从bin文件夹获取

1方法就是通用的方法,先在library工程上右键,选择export,然后选择java标签下的jar file,然后将除了src之外的所有文件夹取消勾选,导出即可,如下图:

附件1

附件2

而2方法简单的多。library工程在被其他工程依赖时,会自动在bin目录下生成编译好的jar包,直接拿此jar包用即可。经过反编译查看此jar包与export导出的jar包的内容是一样的,可放心使用。

使用proguard混淆

平时在eclipse打混淆包时,eclipse会自动找到你在project.properties中定义的proguard配置,并通过命令行调用proguard进行混淆、优化。而这里,我们可以使用proguard提供的gui工具进行混淆。

gui工具在android的sdk目录下的tools/proguard/lib/proguardgui.jar,双击打开即可。界面如下:

附件3

1.先点击底部的load configuration,加载写好的配置文件(请先学习proguard的配置语法):

附件4

2.然后右边的选项点到Input/Output选项,如下图:

附件5

3.在上面add input添加要混淆的jar包,add output添加要生成的jar包。(这里很不合理,因为我要生成的jar包肯定是现在还不存在的,但是它在选择框里要求你选择一个已经存在的jar包。没办法,这里我先用”touch des.jar”新建了一个空白的文件,然后才选的)。

4.然后在下面添加依赖jar包。因为我的工程只用到了android.jar,所以就只把它添加进去了。

5.然后左侧的下面几个看不懂的tab就不要点了,也不要点下面的next跟着它的节奏走,因为proguard的配置我们之前已经加载进去了。所以现在直接点Process标签,然后点击最下方的“Process!”按钮。过一会儿就会得到优化好的、混淆的jar包。

如果过程中出现问题,那么一定是proguard的配置文件写的不对!修改此文件,然后重复上述流程。

Proguard配置文件

其实核心还是这个文件,首先,我在网上看到一个通用的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-verbose
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService
-keepclasseswithmembernames class * {
native <methods>;
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}

直接使用此配置作为混淆文件是不行的,我100k左右的jar包经过上述配置混淆后只剩8k,反编译后发现只剩下了寥寥几个类。原来是我的对外接口没有暴露出来,结果被当作无用的代码优化掉了。

因此对外接口需要暴露出来不混淆,在配置文件底部增加:

1
2
3
4
-keep public class com.test.MyAgent {
public <fields>;
public <methods>;
}

其中MyAgent就是我的对外接口。再次混淆就会发现混淆后的jar包果然变大了。

然而,此时发现其实还有一些类是需要对外暴露的。于是继续添加配置:

1
2
3
4
5
6
7
8
9
-keep public class com.test.MyInterface {
public <fields>;
public <methods>;
}
-keep public class * implements com.test.MyInterface {
public <fields>;
public <methods>;
}

这里规定不混淆MyInterface以及实现了MyInterface接口的文件。

然后还有问题,我将所有的枚举都作为内部类放在了一个EnumUtil中,这些枚举外部也有调用的需要。因此添加:

1
2
3
4
5
6
7
8
9
-keep public class com.test.utils.EnumUtil {
*;
}
-keepnames class com.test.utils.EnumUtil$* {
*;
}
-keepattributes InnerClasses

规定不混淆EnumUtil中的内部类即可。

2016.05.22 update:

jar包里如果用到资源文件千万别用反射的方法获取,因为混淆之后会出问题。。。先是我,然后是两个同事都掉进了这篇文章的坑里了。

直接用context.getResources().getIdentify()的方法获取资源id即可。

EOF